New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
overlord/snapstate: do a minimal sanity check on containers #4464
Conversation
bd52a4f
to
ffcbee1
Compare
This introduces validateContainer, that uses snap.Container's Walk to do a minimal sanity check of the container in question. With this, a snap that has an obvious problem due to e.g. a missing or non-executable app, or an unreadable snap.yaml, will fail to install. The error message will alert the user to contact the snap developers, and the system's log will contain additional details meant for the developers themselves.
ffcbee1
to
91b4232
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great, thanks for working on this. I put some ideas/suggestions inline. I wonder if we should add a tiny (i.e. not testing everything that was already unit tested, just one broken test snap and we try to install it) spread test as well? This way we could check that the logging does what we expect it does.
|
||
var files [][]string | ||
for _, app := range info.Apps { | ||
files = append(files, []string{app.Command, ""}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pardon my ignorance, but why an extra empty string (""
) here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the last argument of MakeTestSnapWithFiles
is a [][]string
, with the inner list's first element being the filename, and the second being its contents
} | ||
|
||
path = strings.TrimPrefix(filepath.Clean(path), "/") | ||
if strings.HasPrefix(path, "../") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, some questions: when does this condition happens? And if we don't handle it, is normPath()
as a name maybe a bit too generic? I.e. if I call normPath("../foo")
the result ""
is a bit unexpected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can create a snap with an app whose command is ../../../bin/sh
, in which case there's nothing for the checker to check.
normPath
signals "nothing to do" with the empty string.
maybe i should add a comment :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice if we had a bit of logic that reconstructs the real path knowing two bits of information:
- classic flag for the snap
- effective mount dir for the snap (which depends on 1)
Then we can construct an absolute path by normalizing whatever relative paths we get here and complete the validation.
c.Check(err, Equals, snapstate.ErrMissingPaths) | ||
} | ||
|
||
func (s *checkSnapSuite) TestValidateContainerSnapYamlBadPermsFails(c *C) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pardon my ignorance again, but how is this different from TestValidateContainerEmptyButBadPermFails ? Looks quite similar to me, maybe a comment that highlights the differences? For if it is about permissions of "." vs "meta" vs "meta/snap.yaml" - maybe make it table driven?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'll take a look
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
while the error and yaml could be tabled, the main differences are in the little setups. Maybe I should add a comment explaining what it's doing? highlighting differences
overlord/snapstate/check_snap.go
Outdated
|
||
var ( | ||
ErrBadModes = errors.New("snap is unusable due to bad permissions; contact develper") | ||
ErrMissingPaths = errors.New("snap is unusable due to missing files; contact developr") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo (will fix)
// Lstat(), and guard against loops, and ... huge can of | ||
// worms, and as this validator is meant as a developer aid | ||
// more than anything else, not worth it IMHO (as I can't | ||
// imagine this happening by accident). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OTOH it would be nice to cover a scenario when app.Command
symlink is dangling.
Simplistic resolving can be done like this: https://play.golang.org/p/oDwwA5x6lBo Then provide something like snap.Container.ResolveSymlink()
which in turn would call unsquashfs -ll <snap> <path>
, grab the exact path and the target, accumulate those in walk function as these files may still appear when walking and if they don't have a separate WalkPaths(paths []string, walkfunc)
which calls unsquashfs -ll <snap> <paths>
. Just a thought, though it looks like quite some work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the problem I don't want to address (and tried to point out in the comment) is that a symlink in a squashfs snap can look like
lrwxrwxrwx john/john 10 2018-01-11 08:11 ./baz -> qux -> foo -> bar
in -lls
; I'd have to do two calls, one for the above (to get the target) and one to just -ls
to get the filename, and match them up. Much too error prone for what it's gaining us.
Yes, it would be nicer. I don't think it's worth it (at least for now).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just realised I need to do something about this anyway. Augh.
in the spread tests I use |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Waiting for fixes
df27ee1
to
3328b78
Compare
overlord/snapstate/check_snap.go
Outdated
|
||
if needsrx[path] || mode.IsDir() { | ||
if mode.Perm()&0555 != 0555 { | ||
logger.Noticef("in snap %q: %q should be world-readable and -executable, and isn't: %s", s.Name(), path, mode) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...world-readable and executable...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fair enough (done)
if idx < 0 { | ||
return nil, errBadPath(raw) | ||
} | ||
st.path = st.path[:idx] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're so close to resolving the symlinks now. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Random thought:
// package squashfs
type Readlinker {
Readlink() (string, err)
}
func (s stat) Sys() { return *s }
func (s *stat) Readlink() {
if st.Mode & os.ModeSymlink { return s.linksTo, nil }
return "", errors.New("not a symlink")
}
Then while walking:
err := c.Walk(".", func(path string, info os.FileInfo, err error) error {
...
if info.Mode() & os.ModeSymlink {
if rd, _ := info.Sys().(squashfs.Readlinker); rd != nil {
whereTo, err := rd.Readlink()
...
}
}
....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it'd have to be something like adding Readlink
to the Container
interface, and have snapdir
and squashfs
both implement it, but yeah, something like that is doable. With the existing "have ->
in a filename and watch it being hilarious" caveat.
As I said on IRC, if we want to do that I think it's followup material, not for this initial PR (which has already stagnated enough)
Codecov Report
@@ Coverage Diff @@
## master #4464 +/- ##
=========================================
Coverage ? 78.19%
=========================================
Files ? 453
Lines ? 32058
Branches ? 0
=========================================
Hits ? 25067
Misses ? 4915
Partials ? 2076
Continue to review full report at Codecov.
|
tr -s "\n " " " < error.log | MATCH 'contact developer' | ||
|
||
# give things time to reach the journal | ||
sleep 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(nitpick) you could write a for i in $(seq 120); do if jounralctl -u snapd | grep check_snap; then break; fi; sleep 0.2; done
here but probably not worth it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aha, I see this was already removed.
This introduces validateContainer, that uses snap.Container's
Walk to do a minimal sanity check of the container in question.
With this, a snap that has an obvious problem due to e.g. a missing or
non-executable app, or an unreadable snap.yaml, will fail to
install. The error message will alert the user to contact the snap
developers, and the system's log will contain additional details meant
for the developers themselves.